19 Understanding Modern Computer Performance
Imagine you’re working in Stata, R, or Excel on a dataset that’s a few gigabytes in size. You run a regression, a pivot table, or a machine learning algorithm, and it can sometimes take far longer than you’d expect, given how powerful modern computers are said to be. If your CPU’s packaging boasts “3.5 GHz and 8 cores,” why does your task still run slowly? The reason goes beyond a single “speed” number on the box. To fully appreciate what’s under the hood, you need to understand two big ideas:
- Parallelism: Modern chips do a lot of work simultaneously.
- Memory Bottlenecks: Getting data to and from these fast processing units often becomes the limiting factor.
In this chapter, we’ll explore how your computer executes instructions, why parallelism is so important, and how memory architecture can make or break performance. Even if you never write code at a low level, this knowledge can help you interpret why certain tasks seem quick and others painfully slow. It also gives you a sense of what’s feasible when planning your data analyses or machine learning pipelines.
We’ll keep the number of new concepts to a minimum, focusing on essentials:
- Clock frequency: The basic “pulse” of the processor.
- Instructions per cycle (IPC): How much useful work gets done each pulse.
- Pipelining and SIMD: Techniques for doing more within each pulse.
- Multicore CPUs and GPUs: Scaling up parallel processing.
- Memory bandwidth and latency: Why data movement can overshadow raw compute power.
- Caches: How modern chips try to hide or reduce the cost of accessing memory.
Let’s start by looking at the idea of “speed” from the processor’s perspective.
19.1 How Fast Are Modern Computers?
19.1.1 Clock Frequency: The Pulse of a Processor
When you see a specification like “3.0 GHz” for a CPU, that number refers to the clock frequency. You can think of the clock as the metronome or heartbeat for the CPU’s internal operations.
What is a Clock Cycle?
One “tick” of the clock is called a cycle. At 3 GHz, your CPU’s clock ticks 3 billion times per second. Each tick is a small window in which the CPU’s circuits align to start or continue executing instructions.Frequency Isn’t Everything
In the 1990s and early 2000s, the push for better performance involved raising clock frequencies (e.g., from 1 GHz to 2 GHz, then to 3 GHz, and so on). But as frequencies climbed, engineers hit physical limits: transistors running at very high frequencies generate more heat and use more power. Beyond a certain point, these chips become impractical to cool. That’s why modern laptops rarely exceed 4 or 5 GHz. The era of “just make it faster by raising GHz” is mostly over.
Analogy: Think of a drummer in a band. Doubling the tempo can push a song’s energy, but there’s a limit to how fast the drummer can go before the music becomes chaos—or the drummer collapses from exhaustion! Similarly, your CPU can’t just keep increasing its tempo (clock frequency) indefinitely.
19.1.2 Instructions per Cycle (IPC): Doing More Each Tick
Even if the CPU runs at 3 GHz, that doesn’t mean it only does 3 billion operations per second. Modern processor designs can handle multiple instructions in the same clock cycle. This measure is referred to as instructions per cycle (IPC).
Superscalar Execution
Most current CPUs can issue multiple instructions to different internal units at once—like having multiple parallel assembly lines. If your code’s instructions don’t conflict (i.e., they don’t need the same resources or wait on each other’s results), the CPU can achieve an IPC above 1.Out-of-Order Execution
For tasks that involve a series of computations, some instructions might be waiting on the results of previous instructions. Modern CPUs can look ahead and reorder independent instructions to avoid idle time. This means that if one instruction is stalled (e.g., waiting for data from memory), the CPU can keep other instructions moving along.
A Simple Example:
- Suppose your CPU can handle two instructions simultaneously in one cycle (IPC=2).
- If your program has 100 instructions where each instruction is independent of the previous one, the CPU might finish those 100 instructions in 50 cycles instead of 100 cycles.
- That effectively doubles the speed.
Analogy: Picture a small bakery that can bake bread and make pastries at the same time, provided the oven (for bread) and the pastry table (for pastries) aren’t both needed by the same item. If there’s a backlog of tasks, the bakery can reorganize them so that the oven is always in use and the pastry table is busy. That’s akin to a CPU dynamically scheduling instructions to different internal execution units.
19.1.3 The CPU Pipeline: An Assembly Line for Instructions
A CPU instruction typically goes through several stages: fetching it from memory (or cache), decoding it, executing it, possibly accessing data in memory, and finally writing the result back to a register. Rather than handle one instruction from start to finish before moving on to the next, pipelining allows multiple instructions to overlap in different stages.
- Stages (A Rough Sketch):
- Fetch: The CPU retrieves an instruction from cache or memory.
- Decode: The instruction is interpreted (e.g., “Add these two numbers”).
- Execute: The CPU carries out the arithmetic or logical operation.
- Memory Access: If the instruction needs to load or store data to memory, that occurs here.
- Write-Back: The result is written into a register (the CPU’s “scratch pad”).
- Fetch: The CPU retrieves an instruction from cache or memory.
- Pipeline Depth
A CPU might have many pipeline stages. One instruction might be in the “fetch” stage, another in “decode,” and so forth. By “lining up” instructions, the CPU can work on multiple at once.
Analogy: In a car factory assembly line, each station does a small job on the partially completed vehicle. After each clock “tick,” the cars roll forward one station. This keeps all stations busy. A pipeline is good as long as you can keep feeding it instructions that don’t trip each other up.
19.1.4 SIMD (Single Instruction, Multiple Data): Working on Batches of Data
When analyzing large datasets in statistical software, you often apply the same operation to many elements. For example, you might add 1 to every entry in a variable’s column. Modern CPUs have specialized vector instructions, collectively known as SIMD, that let you process multiple data points in one instruction.
Vector Registers:
- These are wide registers (e.g., 128-bit, 256-bit, or even 512-bit wide).
- A single register might hold 8 or 16 single-precision numbers.
- These are wide registers (e.g., 128-bit, 256-bit, or even 512-bit wide).
Example:
If you have a 256-bit register that can hold 8 single-precision floats, one SIMD “add” instruction can add 1 to all 8 numbers at once. Contrast that with doing 8 separate add operations in a loop.Benefit:
- Great speedups when you have large arrays and each element needs the same computation.
- This is extremely common in numerical computing (e.g., matrix multiplication, vector transformations).
- Great speedups when you have large arrays and each element needs the same computation.
Analogy: You have a stack of papers, and you need to stamp each one. A “scalar” approach is stamping them one at a time. A “SIMD” approach is using a big, multi-section stamp that can press multiple identical stamps at once. If your papers are well-arranged (aligned data in memory), you can stamp them all quickly.
19.1.5 Multicore CPUs: Multiple Full-Fledged Processors on One Chip
Check your computer’s specs: “4 cores, 8 cores, 16 cores”—what does that mean? Each core is effectively its own CPU, though they share some resources like memory interfaces or caches. Having multiple cores means your computer can truly do multiple tasks at the same time.
- Why Multicore?
- Around 2005, CPU makers faced power and heat limits. Instead of pushing clock speeds ever higher, they started adding more “brains” on the processor.
- Software that can split tasks among multiple cores sees huge speedups. For example, if your data analysis can run in parallel, doubling the number of cores might roughly halve the processing time.
- Around 2005, CPU makers faced power and heat limits. Instead of pushing clock speeds ever higher, they started adding more “brains” on the processor.
- Shared vs. Private Caches
- Each core typically has private L1 and L2 caches (fast memory right next to the core).
- There might be a larger, shared L3 cache that all cores can tap into, reducing the time they must wait for main memory.
- Each core typically has private L1 and L2 caches (fast memory right next to the core).
- Multithreading (like Intel’s Hyper-Threading):
- Each core might appear as two “virtual cores,” which can help fill any downtime when one thread is waiting for data. However, if both threads heavily compete for the same resources (e.g., memory bandwidth), the speedup will be modest.
Analogy: Think of a kitchen with multiple chefs, each with their own small workstation (L1/L2 caches). They might share a larger pantry (L3 cache). If all the chefs are cooking the same dish in parallel, they can finish more meals in the same amount of time—provided they don’t crowd each other out in the pantry or run out of ingredients (memory bandwidth).
19.1.6 GPUs: Originally for Graphics, Now for AI and Beyond
A Graphics Processing Unit (GPU) has become the powerhouse behind many AI breakthroughs. Originally, GPUs were designed to accelerate video rendering, which requires drawing millions of pixels simultaneously. This same property—performing many identical operations in parallel—makes GPUs ideal for matrix-heavy machine learning tasks, like training neural networks.
- Thousands of Cores
- A GPU may have hundreds or thousands of relatively simple “cores.”
- Each is not as flexible as a CPU core, but collectively they can handle vast arrays of numbers.
- A GPU may have hundreds or thousands of relatively simple “cores.”
- High Throughput
- The typical clock speed might be lower (1–2 GHz) compared to a CPU.
- But the total arithmetic power can be enormous, measured in teraflops (trillions of floating-point operations per second) when you sum across all GPU cores.
- The typical clock speed might be lower (1–2 GHz) compared to a CPU.
- Memory System
- GPUs typically have specialized high-bandwidth memory (e.g., GDDR6 or HBM). This helps feed large amounts of data quickly to the many GPU cores.
- One constraint is that you need to transfer data from the CPU’s memory to the GPU’s memory before you can compute on it, which can be a bottleneck if you do it repeatedly.
- GPUs typically have specialized high-bandwidth memory (e.g., GDDR6 or HBM). This helps feed large amounts of data quickly to the many GPU cores.
Analogy: A CPU is like a well-equipped workshop with a few master craftspeople who can handle many varied tasks. A GPU is more like a huge team of specialists, each only able to do a narrower set of tasks, but when you give them the right job (mass production of identical items), they’ll outproduce the CPU workshop by an enormous margin.
19.2 The Memory Bottleneck
We’ve seen that CPUs and GPUs can be incredibly fast, especially when they use pipelines, SIMD instructions, or parallel cores. Yet when you run large-scale data analyses or big machine learning jobs, you might notice performance is far below these “theoretical” peaks. The culprit is often the memory bottleneck: the system’s inability to deliver data to the processors as fast as they can crunch it.
19.2.1 Two Key Terms: Bandwidth and Latency
When we talk about reading or writing data from main memory (the computer’s RAM), we typically focus on two metrics:
Memory Bandwidth
- Think of this as the “width of the highway” that connects your CPU or GPU to main memory. Measured in gigabytes per second (GB/s).
- A typical modern system might have 30–50 GB/s of bandwidth between the CPU and memory (some high-end servers might reach 100+ GB/s).
- GPUs can have memory bandwidth in the hundreds of GB/s or even above a terabyte per second.
- Think of this as the “width of the highway” that connects your CPU or GPU to main memory. Measured in gigabytes per second (GB/s).
Memory Latency
- How long does it take for the first piece of data to arrive once you request it?
- Measured in nanoseconds (ns). Common latencies might be on the order of 50–100 ns, which translates to dozens or hundreds of CPU cycles at multi-gigahertz clock rates.
- If you request a piece of data that’s not already in a cache, your CPU might be “waiting” for tens or hundreds of cycles for that data to arrive.
- How long does it take for the first piece of data to arrive once you request it?
Analogy:
- Bandwidth: Like the number of lanes on a freeway. You can move many cars in parallel if the freeway has multiple lanes.
- Latency: The time it takes for the first car to get from point A to point B if it’s the only one on the road. Even if the freeway is wide, if you need just one car right away, you have to wait the entire travel time.
19.2.2 Caches: Hiding the Slow Speed of Main Memory
Because RAM is relatively slow compared to CPU logic, modern processors use caches—small, fast memory layers—to store data that is likely to be accessed soon. If the data is already in a cache, the CPU can grab it quickly. The main levels are:
- L1 Cache
- Closest to each CPU core, extremely fast (maybe only a few CPU cycles to access).
- Very small (often 32 KB or 64 KB).
- Closest to each CPU core, extremely fast (maybe only a few CPU cycles to access).
- L2 Cache
- Larger than L1 (hundreds of KB).
- Slightly slower to access, but still much faster than main memory.
- Larger than L1 (hundreds of KB).
- L3 Cache
- Even larger (several MBs), often shared among all cores on a CPU.
- Access times can be tens of cycles, but still far better than going to RAM.
- Even larger (several MBs), often shared among all cores on a CPU.
- Main Memory (RAM)
- Gigabytes in size, but accessing data can cost hundreds of cycles due to latency.
- Bandwidth also limits how quickly you can stream large volumes of data.
- Gigabytes in size, but accessing data can cost hundreds of cycles due to latency.
Cache Lines:
- Data in caches is stored in small chunks called “cache lines,” often 64 bytes. When you request one number from memory, the CPU will fetch the entire line containing that number. This helps if you later request neighboring numbers, known as spatial locality.
Analogy:
- L1 cache is like the small set of tools right on your workbench. Instant access.
- L2 cache is like the tool cabinet nearby: it takes a little time to step over and grab something.
- L3 cache is like the shared supply closet for the building: bigger, but slower to access.
- Main memory is akin to driving across town to buy supplies. If you have to make that trip every time you need a nail, your project will stall frequently.
19.2.3 How Memory Bottlenecks Appear in Everyday Analysis
- Large Datasets:
- Suppose you have a dataset with millions of rows, each row containing many columns. Accessing each row might cause multiple cache lines to be fetched. If your analysis jumps around the dataset randomly, you’ll get lots of “cache misses,” meaning the requested data isn’t in L1 or L2, so you pay the high cost of going out to main memory.
- Sequential vs. Random Access:
- If you read data sequentially (e.g., scanning through a column in a contiguous block), the CPU can fetch entire cache lines and get many items in each line. This is cache-friendly.
- If your program jumps around (like looking up random records scattered throughout memory), each fetch might bring in a cache line that mostly goes unused. This drastically increases the number of misses and slows performance.
- If you read data sequentially (e.g., scanning through a column in a contiguous block), the CPU can fetch entire cache lines and get many items in each line. This is cache-friendly.
- Disk I/O:
- In some cases, your data might not even fit in main memory and the system starts swapping to disk or accessing a database on disk. Then you’re limited by disk speeds, which are orders of magnitude slower than RAM. That can make your analyses run many times slower.
Real-World Example:
- You do a pivot table on 10 million rows in Excel. If Excel tries to randomly reorganize or sample data for intermediate calculations, it might cause many memory accesses that blow up the runtime. On a smaller dataset, all those rows might fit into cache or main memory easily in a more sequential manner, so it’s fast.
19.2.4 Illustrating CPU vs. Memory with a Simple Summation Task
Hypothetical Task: You want to sum 1 billion numbers (1,000,000,000) stored in an array in RAM. Each number is a 4-byte single-precision float, so the total data size is 4 GB.
Arithmetic: Summation is trivial for the CPU. Modern CPUs can do billions of additions per second. If you had perfect conditions, you might guess it should take just a fraction of a second to add them all up.
Memory Bandwidth: If your system can read data at 20 GB/s from RAM, reading 4 GB of data requires at least 0.2 seconds. So that’s already a hard lower bound—no matter how fast the CPU is, it can’t sum data it hasn’t received yet.
Cache Effects: Because you only read each number once, the CPU can’t benefit much from reusing data in the cache. So the sum operation is effectively “memory bound.” If you do a simple streaming read of the entire array, the best you can hope for is approaching that 20 GB/s limit.
Random Access: If you read the same data in a random pattern, you might lose some of that bandwidth efficiency. A random pattern prevents large contiguous transfers and can cause more overhead. In practice, it might take even longer than 0.2 seconds.
In short, the CPU is waiting for data to arrive from memory. The arithmetic part is no longer the bottleneck—the memory system is.
19.2.5 A Larger Example: Matrix Multiplication
19.2.5.1 Set the Scene
Matrix multiplication is a fundamental operation in statistics, econometrics, and machine learning. For example, linear regression ( = + ) involves matrix operations, and neural network training involves repeated matrix multiplications for forward passes and backpropagation. Let’s consider two ((10{,}000 000)) matrices as a (slightly extreme) example:
- Matrix Size: Each matrix has (10{,}000 000 = 100{,}000{,}000) (100 million) entries.
- Memory Footprint:
- If using single-precision floats (4 bytes each), each matrix is ~400 MB. Two inputs + one output = around 1.2 GB total.
- Modern desktop or server machines might have enough memory to hold these, but it’s still a hefty chunk.
- If using single-precision floats (4 bytes each), each matrix is ~400 MB. Two inputs + one output = around 1.2 GB total.
19.2.5.2 Naive Operation Count
A straightforward multiplication of two ((N N)) matrices requires about (2N^3) floating-point operations (multiplication + addition). At (N = 10{,}000), that’s (2 ^{12}) operations (2 trillion FLOPs).
- CPU Theoretical Speed:
- A modern CPU might achieve, say, 500 gigaflops (500 billion operations per second) across multiple cores with SIMD under ideal conditions. Finishing 2 trillion operations at 500 GFLOPs would take about 4 seconds in the best case.
- But that doesn’t account for memory effects or overhead from orchestrating the multiplication.
- A modern CPU might achieve, say, 500 gigaflops (500 billion operations per second) across multiple cores with SIMD under ideal conditions. Finishing 2 trillion operations at 500 GFLOPs would take about 4 seconds in the best case.
- Data Movement:
- You must load the input matrices from memory and store the result. Merely reading 1.2 GB from memory might take a fraction of a second on a system with ~50–100 GB/s bandwidth. However, actual multiplications typically require re-reading some portions of data multiple times unless you use a very optimized approach (like a “blocked” matrix multiplication that tries to keep parts of the matrices in cache as long as possible).
Practical Reality:
- Highly optimized linear algebra libraries (e.g., BLAS implementations, Intel MKL, OpenBLAS) use techniques that break the problem into smaller tiles that fit into cache. They reorder the computations to minimize how often they must go to RAM. This can reduce memory transfers significantly and push performance closer to the CPU’s arithmetic limits.
- Even so, you might see actual run times of several seconds to tens of seconds for this large multiplication, depending on your hardware and library optimizations.
19.2.6 GPUs in Action: Speed and Constraints
A modern GPU can have 10 or more teraflops of peak performance in single precision—20 times more than the 500 GFLOPs we estimated for a CPU. On paper, it could do 2 trillion operations in 0.2 seconds. Real performance, though, also depends on memory:
- GPU Onboard Memory:
- High bandwidth (hundreds of GB/s to 1+ TB/s).
- But typically smaller capacity than your computer’s main memory. If your data is bigger than the GPU’s memory, you may have to break it up, causing overhead when transferring data in chunks.
- High bandwidth (hundreds of GB/s to 1+ TB/s).
- PCIe Transfer:
- Data is transferred between the CPU’s memory and GPU’s memory via the PCI Express bus, typically at 16–32 GB/s. If you have to do this transfer constantly, it can become a bottleneck.
Hence, while GPUs excel at tasks that map well to their parallel architecture (like large matrix multiplication, deep learning), you must handle the overhead of data movement carefully. Once the data is on the GPU, it can be processed extremely quickly, provided you keep the GPU fed with data in an efficient manner.
Analogy: Think of the GPU as a specialized factory in another city. Once you ship your raw materials there, the factory can produce goods at an incredible rate. But you need to pay the shipping cost to and from that factory. If you’re sending small batches back and forth constantly, you might lose the advantage. If you send a big batch once, have it all processed, and then bring back the results, you can harness its huge throughput.
19.3 Conclusion
Modern computer performance hinges on a balance between computational power (billions of operations per second from CPU pipelines, SIMD units, or GPU cores) and memory efficiency (getting data in and out of these pipelines without stalling). While CPUs have evolved to run at multiple gigahertz and incorporate clever parallelism (out-of-order execution, pipelining, multicore designs), and GPUs scale parallelism even further, the memory bottleneck—both its bandwidth and latency aspects—still frequently dictates real-world performance in data analysis and machine learning tasks. Understanding these basics helps you anticipate why some computations run instantaneously while others tie up your machine for minutes or hours, and it guides you in making more informed decisions about data layout, choice of hardware (CPU vs. GPU), and reliance on optimized libraries.